
In this assignment we will use all the skills in ensemble learning we acquire from previous exercises to build a an automated fraud detection system.
Throughout this notebook you will find empty cells that you will need to fill with your own code. Follow the instructions in the notebook and pay special attention to the following symbols.
![]() | You will need to solve a question by writing your own code or answer in the cell immediately below, or in a different file as instructed. Both correctness of the solution and code quality will be taken into account for marking. |
![]() | This is a hint or useful observation that can help you solve this assignment. You are not expected to write any solution, but you should pay attention to them to understand the assignment. |
![]() | This is an advanced and voluntary excercise that can help you gain a deeper knowledge into the topic. This exercise won't be taken into account towards marking, but you are encouraged to undertake it. Good luck! |
To avoid missing packages and compatibility issues you should run this notebook under one of the recommended Ensembles environment files.
The following code will embed any plots into the notebook instead of generating a new window:
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
Lastly, if you need any help on the usage of a Python function you can place the writing cursor over its name and press Caps+Shift to produce a pop-out with related documentation. This will only work inside code cells.
Let's go!
The data for this problem is included in the data folder, with separate files for training and test data. Each file includes several unidentified explanatory features, together with an "Amount" feature and the target "Class". Fraudulent operations are marked as Class == 1.
import pandas as pd
import numpy as np
![]() | Load the training and test data into Pandas DataFrames with names train and test, respectively. |
####### INSERT YOUR CODE HERE
train = pd.read_csv('./data/fraud_train.csv', encoding = 'utf-8')
test = pd.read_csv('./data/fraud_test.csv', encoding = 'utf-8')
![]() | Analyze the training data. How many explanatory variables do you have? What is the distribution of classes? |
train.shape
test.shape
####### INSERT YOUR CODE HERE
sns.pairplot(train, hue = "Class")
En el gráfico de arriba podemos ver las correlaciones entre todas las variables, asà como la división (en algunos casos, pues en otros scatterplot vemos las dos clases juntas) entre las operaciones en las que se hizo fraude y aquellas en las que no.
train.describe()
train.dtypes
train.isnull().sum()
Como vemos, no tenemos valores ausentes en ninguna variable.
Sólo el 4% de nuestras observaciones pertenecen a la clase 1 (hace fraude). Esto seguramente dificultará el análisis, pues las clases no están balanceadas, y en cualquier clasificador podrá obtener un error muy pequeño prediciendo siempre que no va a hacer fraude el cliente. Tenemos 5246 observaciones; es decir no demasiadas, por lo que tendremos que tener cuidado a la hora de utilizar modelos demasiado complejos (Random Forest o XGB con demasiados estimadores con respecto al número de observaciones).
Lo primero que nos llama la atención de lo que vemos es el gran número de variables que tenemos. Se observan algunas relaciones lineales bastante claras entre las variables, y otras no tan lineales ni tan fuertes. Vamos a analizar la variable objetivo.
sns.countplot(x='Class',data=train, palette='hls')
plt.show()
En el gráfico superior podemos ver que las clases están muy desbalanceadas, como ya comentábamos antes. Al graficarlo podemos hacernos una idea visual de dicho desbalanceo.
plt.rcParams['figure.figsize'] = (10, 10)
sns.distplot(train['Amount'])
plt.show()
Vemos que hay verdaderamente pocas operaciones con una cantidad (Amount) superior a 1000 dólares.
Fraudulent activities are usually prosecuted, therefore fraudsters need to be creative and come up constantly with new ways of performing fraud. Furthermore, frauds are scarce (fortunately), and so we have few positive class patterns available for training. Because of these facts, it might make sense to build an unsupervised fraud detector.
Dentro del aprendizaje no supervisado, podemos encontrar diferentes métodos o modelos que se suelen utilizar para la detección de anomalÃas, y en concreto en el caso de uso del fraude. Dos ejemplos son este y este. Un artÃculo muy interesante sobre la detección de fraude mediante el uso de diferentes algoritmos de Machine Learning lo podemos encontrar aquÃ; en él podemos inspirarnos sobre algunos de los modelos que podemos utilizar para contrastar los resultados del Isolation Forest. Este es el modelo en el que más nos centraremos pues el objetivo es optimizar el aprendizaje de ensembles, pero esto nos servirá para contrastar mejor los resultados y poder darles una proporcionalidad a las magnitudes de error que encontremos. De otra forma no tendrÃamos manera de saber cómo de bien lo estamos haciendo con respecto a una muestra de lo que se podrÃa hacer alternativamente, sólo sabrÃamos cómo lo estamos haciendo con respecto a Isolation Forests con otros hiperparámetros.
Una forma de afrontar este tipo de problemas serÃa mediante clustering; otras, están basadas en la desviación local de densidad como LOF (Local Outlier Factor), o en Hidden Markov Models. Por otro lado, tenemos las One Class Support Vector Machines, algoritmos modificados de la SVM en los que, mediante aprendizaje no supervisado, tratamos de detectar anomalÃas y outliers. En última instancia, de los más importantes y que más se repiten en los trabajos relativos a esta materia, tenemos los AutoEncoders, un tipo de algoritmo de Deep Learning, también no supervisado, que se utiliza a menudo para la reducción de la dimensión, asà como para la detección de anomalÃas en los datos y por lo tanto también para luchar contra el fraude.
def RocPlot(true, **kwargs):
plt.figure(figsize=(15, 5))
for model in kwargs:
fpr, tpr, _ = roc_curve(true, kwargs[model])
roc = roc_auc_score(true, kwargs[model])
plt.plot(fpr, tpr, lw=2, label='%s (area = %0.3f)' % (model, roc))
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc="lower right")
def plot_confusion_matrix(y_true, y_pred, classes,
normalize=False,
title=None,
cmap=plt.cm.Greens):
"""
This function prints and plots the confusion matrix.
Normalization can be applied by setting `normalize=True`.
"""
if not title:
if normalize:
title = 'Normalized confusion matrix'
else:
title = 'Confusion matrix, without normalization'
# Compute confusion matrix
cm = confusion_matrix(y_true, y_pred)
# Only use the labels that appear in the data
#classes = classes[unique_labels(y_true, y_pred)]
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
print("Normalized confusion matrix")
else:
print('Confusion matrix, without normalization')
print(cm)
fig, ax = plt.subplots()
im = ax.imshow(cm, interpolation='nearest', cmap=cmap)
ax.figure.colorbar(im, ax=ax)
# We want to show all ticks...
ax.set(xticks=np.arange(cm.shape[1]),
yticks=np.arange(cm.shape[0]),
# ... and label them with the respective list entries
xticklabels=classes, yticklabels=classes,
title=title,
ylabel='True label',
xlabel='Predicted label')
# Rotate the tick labels and set their alignment.
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",
rotation_mode="anchor")
# Loop over data dimensions and create text annotations.
fmt = '.2f' if normalize else 'd'
thresh = cm.max() / 2.
for i in range(cm.shape[0]):
for j in range(cm.shape[1]):
ax.text(j, i, format(cm[i, j], fmt),
ha="center", va="center",
color="white" if cm[i, j] > thresh else "black")
fig.tight_layout()
return ax
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV, StratifiedKFold
from sklearn.ensemble import IsolationForest
from sklearn.svm import OneClassSVM
from sklearn.metrics import roc_auc_score, make_scorer, roc_curve, recall_score, classification_report, confusion_matrix
from sklearn.mixture import GaussianMixture
from sklearn.cluster import KMeans, DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
import matplotlib as mpl
from matplotlib.colors import ListedColormap
from sklearn.neighbors import LocalOutlierFactor
from yellowbrick.classifier import ClassificationReport
X_train, y_train = train.drop('Class', axis = 1), train['Class']
X_test, y_test = test.drop('Class', axis = 1), test['Class']
contam = X_train.loc[y_train == 1, :].shape[0]/X_train.shape[0]
res = {}
ocsvmparams = {'kernel':['rbf', 'linear'],
'gamma':['scale', 'auto', 0.1],
'nu':[0.05, 0.95*contam],
'random_state':[69],
'verbose':[True]}
skfold = StratifiedKFold(n_splits = 3)
folds = list(skfold.split(X_train, y_train))
ocsvmgrid = GridSearchCV(estimator = OneClassSVM(),
param_grid = ocsvmparams,
scoring = 'roc_auc', n_jobs = -1,
cv = folds, verbose = 1)
ocsvmgrid.fit(X_train, y_train)
ocsvmgrid.best_estimator_
preds = ocsvmgrid.predict(X_test)
preds = [1 if pred==-1 else 0 for pred in preds]
res['SVC'] = {}
res['SVC']['preds'] = preds
print(classification_report(y_test, preds))
confusion_matrix(y_test, preds)
Como vemos tanto en el classification report, como en la matriz de confusión, como en el gráfico del ROC, este modelo es bastante malo.
RocPlot(y_test, model= -ocsvmgrid.decision_function(X_test))
plot_confusion_matrix(y_true=y_test, y_pred= preds, classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for SVC',
cmap=plt.cm.Greens)
Primero vamos a probar un IsolationForest simple, sin hacer nada de hyperparameter tuning, solo para ver cómo se comporta el algoritmo y, sin modificarle demasiado, qué tal detecta el fraude.
iso = IsolationForest(contamination=train[train['Class']==1].shape[0]/train.shape[0])
iso.fit(X_train)
preds = iso.predict(X_test)
preds = [0 if pred==1 else 1 for pred in preds]
RocPlot(y_test, model = -iso.decision_function(X_test))
![]() | Using only the training data, create an anomaly detection model. You should also choose an error metric adequate for the problem, and tune the model parameters in order to optimize this error. |
X_train, y_train = train.drop('Class', axis = 1), train['Class']
X_test, y_test = test.drop('Class', axis = 1), test['Class']
param_grid = {'n_estimators':[200, 500, 800, 1000],
'max_samples':['auto', 0.7],
'max_features':[1.0, 0.9, 0.8],
'bootstrap':[True, False],
'n_jobs':[-1],
'random_state':[69],
'verbose':[1],
'contamination':[0.04, 0.10, 0.20]}
scores = {'Recall':make_scorer(recall_score,greater_is_better = True, pos_label = -1, average='macro'), 'AUC': 'roc_auc'}
skfold = StratifiedKFold(n_splits = 3)
folds = list(skfold.split(X_train, y_train))
isogrid = GridSearchCV(estimator = IsolationForest(behaviour='new'), param_grid=param_grid,
scoring = scores,
n_jobs = -1, cv = folds, verbose = 1,refit = 'Recall')
np.unique(y_train)
isogrid.fit(X_train, y_train)
isogrid.best_estimator_
preds = isogrid.predict(X_test)
preds = [0 if pred==1 else 1 for pred in preds]
res['ISOFO1'] = {}
res['ISOFO1']['preds'] = preds
confusion_matrix(y_test, preds)
print(classification_report(y_test, preds))
plot_confusion_matrix(y_true=y_test, y_pred= preds, classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for Isolation Forest 1',
cmap=plt.cm.Greens)
isogrid.decision_function(X_test)
RocPlot(y_test, model = -isogrid.decision_function(X_test))
Vamos a intentar mejorar el Isolation Forest de arriba utilizando como scoring el AUC en lugar de el Recall y el AUC. Además, le introduciremos la contaminación real de la muestra, dejando éste de ser un parámetro a optimizar.
contam
param_grid = {'n_estimators':[500, 800, 1000],
'max_samples':['auto', 0.7],
'max_features':[1.0, 0.9, 0.8],
'bootstrap':[True, False],
'n_jobs':[-1],
'random_state':[69],
'verbose':[1]}
score = 'roc_auc'
skfold = StratifiedKFold(n_splits = 3)
folds = list(skfold.split(X_train, y_train))
isogrid2 = GridSearchCV(estimator = IsolationForest(behaviour='new', contamination=contam), param_grid=param_grid,
scoring = score,
n_jobs = -1, cv = folds, verbose = 1)
isogrid2.fit(X_train, y_train)
preds = isogrid2.predict(X_test)
preds = [0 if pred==1 else 1 for pred in preds]
res['ISOFO2'] = {}
res['ISOFO2']['preds'] = preds
print(classification_report(y_test, preds))
Vemos que en este caso sube un poco el recall del fraude, asà como el f1-score
plot_confusion_matrix(y_true=y_test, y_pred= preds, classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for Isolation Forest 2',
cmap=plt.cm.Greens)
Pero sigue habiendo 118 casos en los que dijimos que no habÃa fraude y sin embargo sà lo habÃa.
RocPlot(y_test, model = -isogrid2.decision_function(X_test))
Mejora un poco el AUC con respecto al modelo anterior. PodrÃamos, con el fin de predecir más True Positives (1 verdaderos), sesgar al modelo un poco, haciéndole fallar en algunos 0s (es decir, prediciendo como 1 observaciones en las que no hubo fraude), ya que estos casos son menos dañinos para la compañÃa que los casos en los que predecimos que no hay fraude y sin embargo sà lo hay.
Otra de las acciones que podrÃamos llevar a cabo para tratar de mejorar el IsolationForest es escalar todas las variables (excepto la clase, evidentemente, pues ésta ya está en binario {0,1}). Esto lo aplicaremos a todos los algoritmos que utilicemos más adelante.
clasetr = train['Class']
clasete = test['Class']
scaler = StandardScaler()
scaled_train = scaler.fit_transform(train.drop('Class', axis = 1))
X_train = scaled_train
y_train = clasetr
scaled_test = scaler.fit_transform(test.drop('Class', axis = 1))
X_test = scaled_test
y_test = clasete
contam = X_train[y_train == 1, :].shape[0]/X_train.shape[0]
#y_train = np.array(y_train).reshape(-1, 1)
#y_test = np.array(y_test).reshape(-1, 1)
param_grid = {'n_estimators':[500, 800, 1000],
'max_samples':['auto', 0.7, 0.5],
'max_features':[1.0, 0.5, 0.8],
'bootstrap':[True],
'n_jobs':[-1],
'random_state':[69],
'verbose':[1]}
score = 'roc_auc'
skfold = StratifiedKFold(n_splits = 3)
folds = list(skfold.split(X_train, y_train))
isogrid3 = GridSearchCV(estimator = IsolationForest(behaviour='new', contamination=contam), param_grid=param_grid,
scoring = score,
n_jobs = -1, cv = folds, verbose = 1)
isogrid3.fit(X_train, y_train)
isogrid3.best_estimator_
preds = isogrid3.predict(X_test)
preds = [0 if pred==1 else 1 for pred in preds]
res['ISOFO3'] = {}
res['ISOFO3']['preds'] = preds
plot_confusion_matrix(y_true=y_test, y_pred= preds, classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for Isolation Forest 3',
cmap=plt.cm.Greens)
print(classification_report(y_test, preds))
RocPlot(y_test, model = -isogrid3.decision_function(X_test))
Parece que ahora hemos conseguido mejorar un poco el modelo, aumentando el AUC y el Recall de los 1.
Aprovechando que tenemos un conjunto de entrenamiento con algunas clases predichas, vamos a diseñar una función que encuentre el mejor modelo de cluster y la mejor forma de asignar un cluster a la clase de operación fraudulenta. Por ello mezclaremos una técnica de aprendizaje no supervisado puro con el uso de etiquetas (hay que aprovechar siempre la información que se tiene).
models = ["KMeans", "GaussianMixture", "DBSCAN"]
scaler = StandardScaler()
var_name = "Class"
Xtrain = pd.DataFrame(scaler.fit_transform(train.drop(var_name, axis = 1)))
ytrain = train[var_name]
Xtest = pd.DataFrame(scaler.fit_transform(test.drop(var_name, axis = 1)))
ytest = test[var_name]
def get_fraudulent_cluster(models, train, test, max_clusters = 20, seed = 69, var_name = 'Class'):
'''
This function uses an unsupervised
Parameters:
- model: the unsupervised model used to fit the data.
- train: the train set (including the target if exist)
- max_clusters: the maximum number of clusters to fit.
- seed
- var_name: the name of the Target variable (if exists)
Returns:
- results: a dictionary with the best models for each type of model.
'''
scaler = StandardScaler()
results = {}
#fraud_index = train.index[train[var_name]==1].tolist()
X = pd.DataFrame(scaler.fit_transform(train.drop(var_name, axis = 1)))
Xtest, ytest = pd.DataFrame(scaler.fit_transform(test.drop(var_name, axis = 1))), test[var_name]
for model in models:
if model != "DBSCAN":
best_clust = 0
best_opt = 0
for i in range(2, max_clusters):
if model == 'KMeans':
m = KMeans(n_clusters=i, random_state = seed, max_iter = 200)
g = m.fit_predict(X)
elif model == "GaussianMixture":
m = GaussianMixture(n_components=i, random_state = seed, max_iter = 200)
g = m.fit_predict(X)
train['group'] = g
obtained = 0
clus = 0
for a in range(i):
if train[train['group'] == a][train[var_name] == 1].shape[0] > obtained:
obtained = train[train['group'] == a][train[var_name] == 1].shape[0]
clus = a
if obtained > best_opt:
best_clust = clus
best_opt = obtained
gr_test = m.predict(Xtest)
gr_test = [1 if g==best_clust else 0 for g in gr_test]
roc = roc_auc_score(ytest, gr_test)
recall = recall_score(ytest, gr_test)
_clust = {'model': m,
'clusters': best_clust,
'obtained': best_opt,
'predictions': gr_test,
'roc_auc_score': roc,
'recall_score': recall}
results[model] = _clust
elif model == "DBSCAN":
m = DBSCAN(eps = 0.5, min_samples = 10)
g = m.fit_predict(X)
train['group'] = [1 if t == -1 else 0 for t in g]
score = recall_score(y_true = train[var_name], y_pred = train['group'])
pre = m.fit_predict(Xtest)
pre = [1 if t == -1 else 0 for t in pre]
recall = recall_score(ytest, pre)
roc = roc_auc_score(ytest, pre)
_clust = {'model':m,
'score': score,
'recall': recall,
'roc_auc_score': roc,
'predictions': pre}
results[model] = _clust
else:
print("Model not available")
return results
dic = get_fraudulent_cluster(models = models, train = train, test = test)
dic
plot_confusion_matrix(y_true=y_test, y_pred= dic['KMeans']['predictions'], classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for KMeans',
cmap=plt.cm.Greens)
plot_confusion_matrix(y_true=y_test, y_pred= dic['GaussianMixture']['predictions'], classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for Gaussian Mixture',
cmap=plt.cm.Greens)
plot_confusion_matrix(y_true=y_test, y_pred= dic['DBSCAN']['predictions'], classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for DBSCAN',
cmap=plt.cm.Greens)
Como vemos en el diccionario que nos devuelve la función, ninguno de estos métodos de aprendizaje no supervisado es demasiado bueno; el K-Means y GaussianMixture porque no captan suficiente fraude, y por lo tanto tienen un recall score y un AUC demasiado bajo, y el DBSCAN porque predice demasiado fraude, demasiadas anomalÃas, y por lo tanto tampoco es un buen modelo pese a su buen recall score (en el AUC vemos que es bastante peor que el IsolationForest).
res['DBSCAN'] = {}
res['DBSCAN']['preds'] = dic['DBSCAN']['predictions']
res.keys()
Para encontrar el número óptimo de vecinos que necesitamos para identificar mejor las anomalÃas (el fraude), vamos a iterar desde 3 a 30 vecinos, y vamos a ver con qué número de vecinos la clasificación que nos da el modelo deja un AUC más alto.
max_n = 30
auc = 0
opt_n = 0
for i in range(3, max_n):
lof = LocalOutlierFactor(n_neighbors=i, contamination = contam, n_jobs = -1, novelty=True)
fit = lof.fit(Xtrain, y = ytrain)
preds = lof.predict(Xtest)
preds = [1 if pred==-1 else 0 for pred in preds]
auc_i = roc_auc_score(ytest, preds)
if auc_i > auc:
auc = auc_i
opt_n = i
auc, opt_n
Vemos que el número óptimo de vecinos es de 26, mientras que el AUC es de 0.53, bastante por debajo del obtenido por el Isonation Forest. Como vemos, ningún modelo no supervisado se acerca al rendimiento del Isolation Forest para detectar el fraude, por lo que nos quedaremos con este.
lof = LocalOutlierFactor(n_neighbors=26, contamination = contam, n_jobs = -1, novelty=True)
lof.fit(Xtrain, ytrain)
preds = lof.predict(Xtest)
preds = [1 if pred==-1 else 0 for pred in preds]
res['LOF'] = {}
res['LOF']['preds'] = preds
plot_confusion_matrix(y_true=y_test, y_pred= preds, classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for LOF',
cmap=plt.cm.Greens)
![]() | Create a visualization showing the performance of this model over the test data. |
%matplotlib inline
fraud_index = train.index[train["Class"]==1].tolist()
plt.rcParams['figure.figsize'] = (12, 9)
pca = PCA(n_components = 2)
components = pd.DataFrame(pca.fit_transform(Xtrain))
components['Fraud'] = y_train
cmap = plt.cm.PiYG
N = 2
fig, ax = plt.subplots(1,1, figsize=(10,8))
cmaplist = [cmap(i) for i in range(cmap.N)]
# create the new map
cmap = cmap.from_list('Custom cmap', cmaplist, cmap.N)
# define the bins and normalize
bounds = np.linspace(0,N-1 ,N+1)
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
scat = ax.scatter(components.loc[:, 0],components.loc[:, 1],\
c=components['Fraud'],\
#s=np.random.randint(100,500,N),\
cmap=cmap, norm=norm)
# create the colorbar
cb = plt.colorbar(scat, spacing='proportional',ticks=bounds)
cb.set_label('Custom cbar')
ax.set_title('')
plt.show()
Antes de entrar a visualizar las clasificaciones que han hecho nuestros modelos de aprendizaje no supervisado, vamos a ver, para entender mejor el funcionamiento interno de cada modelo y cómo separa los datos, una visualización en la que entrenamos los modelos solo con los dos primeros componentes principales y visualizamos sus regiones de la función de decisión sobre los mismos; primero solo sobre el train set y posteriormente con los modelos entrenados en el train sobre el test set. Esto nos permitirá ver, por ejemplo, que la región de decisión para un SVC es lineal, y cuando los datos no se pueden separar perfectamente de forma lineal tendrá algunos problemas a la hora de identificar anomalÃas; en el caso del LOF veremos una región de decisión basada en gran parte en elipses, lo cual muestra su relación con la estadÃstica gaussiana.
%matplotlib inline
names1 = ['SVC', 'LOF']
names2 = ['ISOLATION FOREST']
models1 = [
OneClassSVM(cache_size=200, coef0=0.0, degree=3, gamma='scale',
kernel='linear', max_iter=-1, nu=0.05, random_state=69,
shrinking=True, tol=0.001, verbose=True),
LocalOutlierFactor(n_neighbors=26, contamination = contam, n_jobs = -1, novelty=True)
#DBSCAN(algorithm='auto', eps=0.5, leaf_size=30, metric='euclidean',
#metric_params=None, min_samples=10, n_jobs=None, p=None),
]
models2 = [IsolationForest(behaviour='new', bootstrap=True,
contamination=0.04689287075867327, max_features=0.8,
max_samples=0.7, n_estimators=500, random_state=69,
verbose=1)]
datasets = [train, test]
dec1 = IsolationForest(behaviour='new', bootstrap=True,
contamination=0.04689287075867327, max_features=0.8,
max_samples=0.7, n_estimators=500, random_state=69,
verbose=1).fit(pd.DataFrame(PCA(n_components=2).fit_transform\
(StandardScaler().fit_transform(train.drop("Class", axis = 1)))))\
.decision_function(pd.DataFrame(PCA(n_components=2).fit_transform\
(StandardScaler().fit_transform(train.drop("Class", axis = 1)))))
dec2 = IsolationForest(behaviour='new', bootstrap=True,
contamination=0.04689287075867327, max_features=0.8,
max_samples=0.7, n_estimators=500, random_state=69,
verbose=1).fit(pd.DataFrame(PCA(n_components=2).fit_transform\
(StandardScaler().fit_transform(train.drop("Class", axis = 1)))))\
.decision_function(pd.DataFrame(PCA(n_components=2).fit_transform\
(StandardScaler().fit_transform(test.drop("Class", axis = 1)))))
dec = [dec1, dec2]
%matplotlib inline
names1 = ['SVC', 'LOF']
names2 = ['ISOLATION FOREST']
models1 = [
OneClassSVM(cache_size=200, coef0=0.0, degree=3, gamma='scale',
kernel='linear', max_iter=-1, nu=0.05, random_state=69,
shrinking=True, tol=0.001, verbose=True),
LocalOutlierFactor(n_neighbors=26, contamination = contam, n_jobs = -1, novelty=True)
#DBSCAN(algorithm='auto', eps=0.5, leaf_size=30, metric='euclidean',
#metric_params=None, min_samples=10, n_jobs=None, p=None),
]
models2 = [IsolationForest(behaviour='new', bootstrap=True,
contamination=0.04689287075867327, max_features=0.8,
max_samples=0.7, n_estimators=500, random_state=69,
verbose=1)]
datasets = [train, test]
def plot_results(names, models, datasets, var_name, h = 0.02, mode="unsupervised", dec = None):
i = 1
figure = plt.figure(figsize=(27, 9), dpi = 50)
for count, data in enumerate(datasets):
pca = PCA(n_components=2)
X, y = StandardScaler().fit_transform(data.drop(var_name, axis = 1)), data[var_name]
X = pca.fit_transform(X)
#X_train = pd.DataFrame(pca.fit_transform(X_train))
#X_test = pd.DataFrame(pca.fit_transform(X_test))
x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
cm = plt.cm.RdYlGn
cm_bright = ListedColormap(['#0cff00','#FF0000'])
ax = plt.subplot(len(datasets), len(models) + 1, i)
if count == 0:
ax.set_title("Input data")
# Plot the training points
ax.scatter(X[:, 0], X[:, 1], c=y, cmap=cm_bright,
edgecolors='k')
# Plot the testing points
ax.scatter(X[:, 0], X[:, 1], c=y, cmap=cm_bright, alpha=0.6,
edgecolors='k')
ax.set_xlim(xx.min(), xx.max())
ax.set_ylim(yy.min(), yy.max())
ax.set_xticks(())
ax.set_yticks(())
i += 1
for name, clf in zip(names, models):
ax = plt.subplot(len(datasets), len(models) + 1, i)
if count == 0:
if hasattr(clf, "fit"):
X_, y_ = pd.DataFrame(pca.fit_transform(
StandardScaler()\
.fit_transform(datasets[count].drop(var_name, axis = 1)\
))), datasets[count][var_name]
clf.fit(X_, y_)
else:
clf.fit_predict(X, y)
else:
try:
X_, y_ = pd.DataFrame(pca.fit_transform(
StandardScaler()\
.fit_transform(datasets[count-1].drop(var_name, axis = 1)\
))), datasets[count-1][var_name]
clf.fit(X_, y_)
except Exception as e:
print(e)
X_, y_ = pd.DataFrame(pca.fit_transform\
(StandardScaler()\
.fit_transform(datasets[count-1].drop(var_name, axis = 1)\
)))
clf.fit_predict(X_, y_)
if mode == "unsupervised":
'''
try:
preds = [1 if pred == -1 else 0 for pred in clf.predict(X)]
except:
try:
preds = [1 if dec == -1 else 0 for dec in clf.decision_function(X)]
except:
try:
preds = [1 if pred == -1 else 0 for pred in clf.fit_predic(X)]
'''
try:
score = roc_auc_score(y, -clf.decision_function(X))
except:
score = roc_auc_score(y, [1 if pred == -1 else 0 for pred in clf.predict(X)])
else:
try:
score = roc_auc_score(y, clf.decision_function(X))
except:
score = roc_auc_score(y, clf.predict_proba(X)[:, 1])
# Plot the decision boundary. For that, we will assign a color to each
# point in the mesh [x_min, x_max]x[y_min, y_max].
if hasattr(clf, "decision_function"):
#Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
try:
Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
except:
Z1 = clf.decision_function(np.c_[xx.ravel()[:int(xx.ravel().shape[0]/2)], \
yy.ravel()[:int(yy.ravel().shape[0]/2)]])
Z2 = clf.decision_function(np.c_[xx.ravel()[int(xx.ravel().shape[0]/2):], \
yy.ravel()[int(yy.ravel().shape[0]/2):]])
Z = np.concatenate(Z1, Z2)
else:
Z = clf.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:, 1]
# Put the result into a color plot
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, cmap=cm, alpha=.8)
# Plot the training points
ax.scatter(X[:, 0], X[:, 1], c=y, cmap=cm_bright,
edgecolors='k')
# Plot the testing points
#ax.scatter(X_test[:, 0], X_test[:, 1], c=y_test, cmap=cm_bright,
# edgecolors='k', alpha=0.6)
ax.set_xlim(xx.min(), xx.max())
ax.set_ylim(yy.min(), yy.max())
ax.set_xticks(())
ax.set_yticks(())
if count == 0:
ax.set_title(name)
ax.text(xx.max() - .5, yy.min() + .8, ('%.2f' % score).lstrip('0'),
size=20, horizontalalignment='right')
i += 1
plt.tight_layout()
plt.show()
Como estamos recibiendo un error constante de memoria, y aunque quede algo peor, vamos a dibujar por un lado los dos primeros modelos y por otro lado el Isolation Forest, a ver si de esta forma no colapsa.
plot_results(names = names1, models = models1, datasets=datasets, var_name = "Class")
plot_results(names = names2, models = models2, datasets=datasets, var_name = "Class")
Pese a los trucos que intentamos hacer nos sigue saliendo el MemoryError. Por lo tanto, los resultados del Isolation Forest los presentaremos de otra forma.
def plot_classification(results, test, metric = "Recall"):
from matplotlib.colors import ListedColormap
h=0.02
X, y = test.drop('Class', axis = 1), test["Class"]
comps = PCA(n_components=2).fit_transform(StandardScaler().fit_transform(X))
x_min, x_max = comps[:, 0].min() - .5, comps[:, 0].max() + .5
y_min, y_max = comps[:, 1].min() - .5, comps[:, 1].max() + .5
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
i = 1
figure = plt.figure(figsize=(27, 9), dpi = 50)
cm = plt.cm.RdYlGn
cm_bright = ListedColormap(['#0cff00','#FF0000'])
ax = plt.subplot(1, len(results.keys())+1, i)
if i == 1:
ax.set_title("Input data")
# Plot the training points
ax.scatter(comps[:, 0], comps[:, 1], c=y, cmap=cm_bright,
edgecolors='k')
# Plot the testing points
ax.scatter(comps[:, 0], comps[:, 1], c=y, cmap=cm_bright, alpha=0.8,
edgecolors='k')
ax.set_xlim(xx.min(), xx.max())
ax.set_ylim(yy.min(), yy.max())
ax.set_xticks(())
ax.set_yticks(())
for model in results:
i += 1
ypred = results[model]['preds']
ax = plt.subplot(1, len(results.keys())+1, i)
if metric == "Recall":
score = recall_score(y, ypred)
elif metric == "AUC":
try:
score = roc_auc_score(y, results[model]['dec_func'])
except:
score = roc_auc_score(y, results[model]['dec_func'][:, 1])
ax.scatter(comps[:, 0], comps[:, 1], c = ypred, cmap=cm_bright,
alpha = 0.8, edgecolors='k')
ax.set_xlim(comps[:, 0].min(), comps[:, 0].max())
ax.set_ylim(comps[:, 1].min(), comps[:, 1].max())
ax.set_xticks(())
ax.set_yticks(())
ax.set_title(model)
ax.text(comps[:, 0].max() - .5, comps[:, 1].min() + .8, ('%.2f' % score).lstrip('0'),
size=50, horizontalalignment='right')
plt.tight_layout()
plt.show()
plot_classification(res, test)
En cada uno de los dos gráficos que se pueden ver arriba, el primer gráfico de los mismos en ambos casos representa los datos tal cual. En el primero de los casos se presentan el train (arriba) y el test (abajo), representados sobre sus componentes principales. En rojo podemos ver los casos en los que no ha habido fraude y en azul vemos los casos en los que sà ha habido fraude. El mejor Isolation Forest en términos de AUC tiene un Recall Score de 0.67 (es el score más importante a la hora de prevenir el fraude, pues nos interesa sobre todo captar todos los verdaderos positivos, aunque esto sea a expensas de clasificar falsos positivos). En los casos de SVC y LOF son de 0.14 y de 0.93 respectivamente. Aunque pueda parecer que el LOF es el mejor (y en términos únicamente de Recall Score lo es), realmente clasifica prácticamente todo como fraude, por lo tanto no es tan sensible como el Isolation Forest, que como vemos hace una separación algo más fina, pues sus decision boundaries pueden tomar más formas, distintas a las que observamos del SVC (basado en separaciones lineales) o en el LOF (basado en distribución Gaussiana - de ahà la forma de elipse de su función de decisión sobre los puntos). Es importante mencionar, de todas formas, que los gráficos de arriba están hechos con los modelos entrenados sobre los dos primeros componentes principales (era la forma de pintar bonitas las regiones de decisión para los distintos treshold y demás), mientras que el Isolation Forest está entrenado sobre todos los datos; esto le da una ventaja a la hora de identificar los patrones de operaciones fraudulanetas.
####### INSERT YOUR CODE HERE
![]() | Let's check now whether we can improve the results using a supervised model, that is, a model that exploits the Class information available in the training data. Build an ensemble-based classification model that performs the best as possible, using only the data in the training set. |
Vamos a hacer hyperparameter tuning con 3 modelos de Boosting a la vez: Random Forest, Adaboost, Gradient Boosting y Extreme Gradient Boosting (XGB). Utilizaremos inicialmente el AUC como métrica para medir nuestro error (o mejor dicho, nuestro grado de acierto, pues cuando mayor es el AUC, mejor clasificamos).
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier
from xgboost import XGBClassifier
from sklearn.utils.multiclass import unique_labels
rf = RandomForestClassifier(warm_start=True)
rf_grid = {'n_estimators': [300, 500, 800, 1000],
'max_depth':[3, 4, 6],
'max_features': ['auto', 'sqrt'],
'criterion': ['gini'],
'bootstrap': [True],
'n_jobs': [-1],
'verbose': [1],
'class_weight': [{0:1, 1:2}, None],
'random_state': [69]}
ada = AdaBoostClassifier()
ada_grid = {'n_estimators': [100, 300, 500, 800, 1000],
'learning_rate': [0.01, 0.05, 0.1, 0.5, 1],
'random_state': [69]}
gb = GradientBoostingClassifier(warm_start=True)
gb_grid = {'n_estimators': [300, 500, 800, 1000],
'learning_rate': [0.01, 0.05, 0.1, 0.5, 1],
'subsample': [0.8, 1.0],
'max_depth':[3, 5],
'random_state': [69],
'validation_fraction': [0.1, 0.2]}
xgb = XGBClassifier()
xgb_grid = {'n_estimators': [300, 500, 800, 1000],
'max_depth':[3, 5],
'learning_rate': [0.01, 0.02, 0.05, 0.1],
'n_jobs': [-1],
'silent': [False],
'colsample_bytree': [0.8, 1],
'reg_lambda': [0.5, 1],
'reg_alpha': [0, 0.5],
'random_state': [69]}
totaldict = {'Random Forest': {'model': rf,
'params': rf_grid},
'AdaBoost': {'model': ada,
'params': ada_grid},
'GradientBoosting': {'model': gb,
'params': gb_grid},
'XGB': {'model': xgb,
'params': xgb_grid}}
train = pd.read_csv('./data/fraud_train.csv', encoding = 'utf-8')
test = pd.read_csv('./data/fraud_test.csv', encoding = 'utf-8')
def get_supervised_results(models, train, test, var_name):
Xtr, ytr = pd.DataFrame(StandardScaler().fit_transform(train.drop(var_name, axis = 1))), train[var_name]
Xte, yte = pd.DataFrame(StandardScaler().fit_transform(test.drop(var_name, axis = 1))), test[var_name]
resu = {}
for model in models:
resu[model] = {}
gridse = GridSearchCV(estimator = models[model]["model"],
param_grid = models[model]['params'],
scoring = make_scorer(roc_auc_score, greater_is_better=True),
n_jobs = -1, verbose = 1, cv = 3)
gridse.fit(Xtr, ytr)
preds = gridse.predict(Xte)
resu[model]['preds'] = preds
resu[model]['model'] = gridse.best_estimator_
if hasattr(gridse, "decision_function"):
resu[model]['dec_func'] = gridse.decision_function(Xte)
resu[model]['auc'] = roc_auc_score(ytest, gridse.decision_function(Xte))
else:
resu[model]['dec_func'] = gridse.predict_proba(Xte)
try:
resu[model]['auc'] = roc_auc_score(ytest, gridse.predict_proba(Xte))
except:
next
return resu
results = get_supervised_results(models=totaldict, train=train, test=test, var_name = "Class")
results['Random Forest']['auc'] = roc_auc_score(y_true = test['Class'], y_score=results['Random Forest']['dec_func'][:, 1])
results['XGB']['auc'] = roc_auc_score(y_true = test['Class'], y_score=results['XGB']['dec_func'][:, 1])
results
y_pred = results['Random Forest']['preds']
np.set_printoptions(precision=3)
class_names = [0, 1]
# Plot non-normalized confusion matrix
plot_confusion_matrix(y_test, y_pred, classes=class_names,
title='Confusion matrix for Random Forest, without normalization')
# Plot normalized confusion matrix
plot_confusion_matrix(y_test, y_pred, classes=class_names, normalize=True,
title='Normalized confusion matrix for Random Forest')
plt.show()
RocPlot(y_test, model=results['Random Forest']['dec_func'][:, 1])
Como vemos el Random Forest tiene un AUC muy alto, bastante superior a los modelos que habÃamos visto hasta ahora; cuand miramos la matriz de confusión también descubrimos con agradable sorpresa que el modelo capta suficientemente bien el fraude sin incurrir den casos de predecir fraude sin que lo sea. Se le escapan sólo el 17% de los casos de fraude en el test al Random Forest
plot_confusion_matrix(y_true=y_test, y_pred= results['AdaBoost']['preds'], classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for AdaBoostClassifier',
cmap=plt.cm.Greens)
RocPlot(y_test, model=results['AdaBoost']['dec_func'])
Como vemos AdaBoost también funciona mejor que los métodos de aprendizaje no supervisado en general, captando la mayorÃa de los casos de fraude; se le escapan un 19% en el test; algo peor que Random Forest.
plot_confusion_matrix(y_true=y_test, y_pred= results['GradientBoosting']['preds'], classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for Gradient Boosting',
cmap=plt.cm.Greens)
RocPlot(y_test, model=results['GradientBoosting']['dec_func'])
Como vemos en la matriz de confusión, Gradient Boosting predice aún más casos de fraude que los dos modelos de ensemble vistos anteriormente, dejándose sólo el 15% de los casos sin predecir. Sin embargo, tiene un AUC algo menor que Random Forest, mostrando que su función de decisión no es tan robusta quizás como la de los dos modelos anteriores. Vamos a ver qué tal resulta XGBoost.
plot_confusion_matrix(y_true=y_test, y_pred= results['XGB']['preds'], classes=[0,1],
normalize=True,
title='Normalized Confusion Matrix for XGB',
cmap=plt.cm.Greens)
RocPlot(y_test, model=results['XGB']['dec_func'][:, 1])
Vemos que XGB tiene un AUC algo inferior a Random Forest, pero por encima de Adaboost o Gradient Boosting. Se deja sin predecir el mismo porcentaje de operaciones fraudulentas que Random Forest.
recalls = [recall_score(y_test, results[m]['preds']) for m in results]
models = [m for m in results]
models
rec_df = pd.DataFrame({'model': models,
'recall':recalls})
rec_df = rec_df.sort_values(by=['recall'], axis=0, ascending=False)
rec_df
![]() | Now create a visualization showing the performance of this supervised model on the test set, together with the unsupervised model. Has the performance improved after making use of the Class data? |
plt.figure(figsize=(15,5))
sns.barplot(y="model", x="recall", data=rec_df)
plot_classification(results, test)
plot_classification(results, test, metric = "AUC")
En los gráficos de arriba podemos ver que el modelo que mejor Recall Score tiene es el Gradient Boosting, seguido de Random Forest y XGB, y por último Adaboost. En el segundo de estos gráficos tenemos los input data, asà como la clasificación que ha hecho cada uno de los modelos. Los puntos verdes en el Input Data son los que realmente no habÃan sido fraude, mientras que los rojos son los que sà han sido fraude. En los gráficos que se ven al lado del Input data podemos ver la clasificación que ha hecho cada uno de los modelos. Primero presentamos el Recall Score, y vemos que el que más tiene es como dijimos el Gradient Boosting con 0.85, XGB y RF tienen 0.83 y Adaboost tiene 0.81. Justo después de este gráfico tenemos uno similar en el que los comparamos en términos de AUC. Vemos que el AUC de todos es muy parecido, siendo Adaboost el que menos tiene.
auc_df = pd.DataFrame({'model': [m for m in results],
'auc': [results[m]['auc'] for m in results]})
plt.figure(figsize=(15,5))
sns.barplot(y="model", x="auc", data=auc_df)
Para ver cómo son las regiones de la función de decisión de estos modelos, vamos a hacer lo mismo que hicimos en la parte de no supervisado, es decir vamos a dibujar las regiones (para los diferentes treshold) de la función de decisión que tendrÃan estos modelos usando sólo los dos primeros componentes principales.
Vemos que en términos de AUC los modelos son prácticamente iguales, y cuesta mucho distinguirles en función de esta métrica visualmente.
names = ['Random Forest','AdaBoost', 'Gradient Boosting', 'XGB']
models = [results[m]['model'] for m in results]
#models
####### INSERT YOUR CODE HERE
from skopt import BayesSearchCV
from sklearn.model_selection import train_test_split
def get_supervised_results_bayes(models, train, test, var_name):
X_train, y_train = pd.DataFrame(StandardScaler().fit_transform(train.drop(var_name, axis = 1))), train[var_name]
X_test, y_test = pd.DataFrame(StandardScaler().fit_transform(test.drop(var_name, axis = 1))), test[var_name]
resu = {}
for model in models:
resu[model] = {}
gridse = BayesSearchCV(estimator = models[model]["model"],
search_spaces = models[model]['params'],
scoring = make_scorer(roc_auc_score, greater_is_better=True),
n_jobs = -1, verbose = 1, cv = 3, n_iter = 50, random_state = 69)
gridse.fit(X_train, y_train)
preds = gridse.predict(X_test)
resu[model]['preds'] = preds
resu[model]['model'] = gridse.best_estimator_
if hasattr(gridse, "decision_function"):
resu[model]['dec_func'] = gridse.decision_function(X_test)
resu[model]['auc'] = roc_auc_score(y_test, gridse.decision_function(X_test))
else:
resu[model]['dec_func'] = gridse.predict_proba(X_test)
try:
resu[model]['auc'] = roc_auc_score(y_test, gridse.predict_proba(X_test))
except:
next
return resu
rf = RandomForestClassifier(warm_start=True)
rf_grid = {'n_estimators': [300, 500, 800, 1000],
'max_depth':[3, 4, 6],
'max_features': ['auto', 'sqrt'],
'criterion': ['gini'],
'bootstrap': [True],
'n_jobs': [-1],
'verbose': [1],
'random_state': [69]}
ada = AdaBoostClassifier()
ada_grid = {'n_estimators': [100, 300, 500, 800, 1000],
'learning_rate': [0.01, 0.05, 0.1, 0.5, 1],
'random_state': [69]}
gb = GradientBoostingClassifier(warm_start=True)
gb_grid = {'n_estimators': [300, 500, 800, 1000],
'learning_rate': [0.01, 0.05, 0.1, 0.5, 1],
'subsample': [0.8, 1.0],
'max_depth':[3, 5],
'random_state': [69],
'validation_fraction': [0.1, 0.2]}
xgb = XGBClassifier()
xgb_grid = {'n_estimators': [300, 500, 800, 1000],
'max_depth':[3, 5],
'learning_rate': [0.01, 0.02, 0.05, 0.1],
'n_jobs': [-1],
'silent': [False],
'colsample_bytree': [0.8, 1],
'reg_lambda': [0.5, 1],
'reg_alpha': [0, 0.5],
'random_state': [69]}
totaldict = {'Random Forest': {'model': rf,
'params': rf_grid},
'AdaBoost': {'model': ada,
'params': ada_grid},
'GradientBoosting': {'model': gb,
'params': gb_grid},
'XGB': {'model': xgb,
'params': xgb_grid}}
resultsbayes = get_supervised_results_bayes(models = totaldict, train = train, test = test, var_name = "Class")
El error que nos da BayesSearchCV es un error interno que no podemos resolver nosotros, por lo tanto continuamos con la siguiente parte.
def stacking_model(models, train, test, var_name):
X_train, X_val, y_train, y_val = train_test_split(StandardScaler().fit_transform(train.drop(var_name, axis = 1)),
train[var_name], test_size = 0.4, random_state = 69)
Xtest, ytest = StandardScaler().fit_transform(test.drop(var_name, axis = 1)), test[var_name]
val = pd.DataFrame(X_val).copy()
te = pd.DataFrame(X_test).copy()
for i, model in enumerate(models):
model.fit(X_train, y_train)
preds = model.predict(X_val)
val['preds' + str(i)] = preds
assert X_train.shape[1] == Xtest.shape[1]
preds2 = model.predict(Xtest)
te['preds'+str(i)] = preds2
xgb = XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bytree=0.8, gamma=0, learning_rate=0.05, max_delta_step=0,
max_depth=3, min_child_weight=1, missing=None, n_estimators=300,
n_jobs=-1, nthread=None, objective='binary:logistic',
random_state=69, reg_alpha=0, reg_lambda=1, scale_pos_weight=1,
seed=None, silent=False, subsample=1)
xgb.fit(val, y_val)
preds = xgb.predict(te)
auc = roc_auc_score(ytest, xgb.predict_proba(te)[:, 1])
recall = recall_score(ytest, preds)
stack = {'preds': preds,
'auc': auc,
'recall': recall,
'model': xgb,
'dec_func': xgb.predict_proba(te)[: , 1]}
return stack
stack_results = stacking_model(models = models, train = train, test = test, var_name ="Class")
stack_results
results['stacking'] = stack_results
auc_df = pd.DataFrame({'model': [m for m in results],
'auc': [results[m]['auc'] for m in results]}).sort_values(by=['auc'], axis=0, ascending=False)
plt.figure(figsize=(15,5))
sns.barplot(y="model", x="auc", data=auc_df)
Vemos que en términos de AUC el Stacking Model serÃa el peor, aunque con una diferencia muy pequeña sobre el resto. Vamos a ver ahora qué tal quedarÃa en términos de Recall.
rec_df = pd.DataFrame({'model': [m for m in results],
'recall': [recall_score(y_test, results[m]['preds']) for m in results]}).sort_values(by=['recall'],
axis = 0,
ascending = False)
plt.figure(figsize=(15,5))
sns.barplot(y="model", x="recall", data=rec_df)
Sin embargo en términos de Recall los resultados son bastante buenos, similares a los de Gradient Boosting. Podemos determinar, por lo tanto, que los mejores modelos para luchar contra el fraude serÃan o bien el Gradient Boosting o bien el stacking model, pues estos son los que más veces predicen si el individuo va o no va a hacer fraude. En general los modelos de aprendizaje supervisado, que explotan la información contenida en la variable "Class" funcionan mejor que los modelos de aprendizaje no supervisado, donde para encontrar las anomalÃas no se cuenta con la información contenida en esta variable y por lo tanto tienen algo menos de conocimiento respecto al problema que se intenta resolver.
plot_classification(results=results, test = test, metric = "Recall")
plot_classification(results=results, test=test, metric="AUC")
Por último vamos a probar un modelo que llamaremos stacking2 que será básicamente coger el voto mayoritario de todos los modelos de ensemble, utilizando todas las predicciones.
results
def voto_mayoritario(models, ytest):
preds_ = []
for i in range(5246):
pr = [models[m]['preds'][i] for m in models]
if sum(pr) >= 3:
preds_.append(1)
else:
preds_.append(0)
recall = recall_score(ytest, preds_)
re = {'recall': recall,
'preds': preds_}
return re
stacking2 = voto_mayoritario(models=results, ytest= test['Class'])
results['stacking2'] = stacking2
Vemos que el recall con el voto mayoritario es algo inferior a los que habÃamos obtenido antes. Podemos decir, finalmente, que nos quedamos por lo tanto preferiblemente con el Gradient Boosting, pues es el modelo que mejor Recall Score tenÃa siendo además alto su AUC (a diferencia del stacking anterior que presentamos). Como hemos visto, tanto visualmente como en números, es que en general los modelos de aprendizaje supervisado son mejores que los modelos de aprendizaje no supervisado a la hora de detectar el fraude. Podemos por tanto utilizar cualquiera de estos modelos (todos tienen una tasa de error muy baja) para luchar contra el fraude, siendo el Gradient Boosting el modelo que en teorÃa nos harÃa perder la menor cantidad de dinero; el stacking primero que presentamos parece que también pilla muy bien los casos de fraude, pero tiene un AUC más bajo, mostrando que lo hace a costa de predecir más veces fraude de las que toca. Estos errores, de todas formas, son más baratos para la compañÃa que el error del otro tipo, es decir predecir que alguien no va a hacer fraude y finalmente lo hace. Por este motivo nos hemos fijado mucho en el AUC y especialmente en el Recall Score a la hora de elegir los modelos, pues estamos especialmente interesados en captar los casos de fraude, aunque esto sea a costa de "acusar" incorrectamente a otra persona.
plot_classification(results = results, test = test, metric = "Recall")
rec_df = pd.DataFrame({'model': [m for m in results],
'recall': [recall_score(y_test, results[m]['preds']) for m in results]}).sort_values(by=['recall'],
axis = 0,
ascending = False)
plt.figure(figsize=(15,5))
sns.barplot(y="model", x="recall", data=rec_df)